# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.


"""Wrapper to start Archiver service."""


import collections
import hashlib
import logging
import os
import pprint
import yaml

import factory_common  # pylint: disable=W0611
from cros.factory import schema
from cros.factory.umpire.service import umpire_service
from cros.factory.utils import file_utils

_DATA_TYPES_SCHEMA = schema.FixedDict(
    'data type in details', items={},
    optional_items={
        'notes': schema.Scalar('notes', str),
        'source_dir': schema.Scalar('source_dir', str),
        'duration': schema.Scalar('duration', str),
        'compress_format': schema.Scalar('compress_format', str),
        'encrypt_key_pair': schema.List('key and recipient')})

CONFIG_SCHEMA = {
    'optional_items': {
        'common': schema.FixedDict('common', items={}, optional_items={
            'archived_dir': schema.Scalar('archived_dir', str),
            'project': schema.Scalar('project', str),
            'duration': schema.Scalar('duration', str)}),
        'data_types': schema.FixedDict('data_types', items={}, optional_items={
            'eventlog': _DATA_TYPES_SCHEMA,
            'reports': _DATA_TYPES_SCHEMA,
            'regcode': _DATA_TYPES_SCHEMA})}}

ARCHIVER_CLI = 'usr/local/factory/py/lumberjack/archiver_cli.py'
REGCODE_DEFAULT_KEY = ('usr/local/factory/py/lumberjack/'
                       'google-crosreg-key.bin.public')
NAME_PREFIX = 'archiver_'  # Prefix for proc name and config path.
NOTES = 'Configuration auto-generated by Umpire archiver service.'


class ArchiverService(umpire_service.UmpireService):
  """Archiver service.

  Example:
    archiver_service = GetServiceInstance('archiver')
    procs = svc.CreateProcesses(umpire_config_attrdict, umpire_env)
    archiver_service.Start(procs)
  """

  def __init__(self):
    super(ArchiverService, self).__init__()

  @staticmethod
  def _RecursivelyUpdateDict(dict_to_update, update_info):
    """The recursive version of dict.update()."""
    for key, value in update_info.iteritems():
      if isinstance(value, collections.Mapping) and key in dict_to_update:
        ArchiverService._RecursivelyUpdateDict(dict_to_update[key], value)
      else:
        dict_to_update[key] = value

  @staticmethod
  def GenerateConfig(umpire_config, env):
    """Generates and returns the path to the config.

    Args:
      umpire_config: Umpire config dict.
      env: UmpireEnv object.

    Returns:
      Path to config (w/ the config's hash in filename).
      None if failed to generate.
    """
    if ('services' not in umpire_config or
        'archiver' not in umpire_config['services']):
      return None

    default_archived_dir = os.path.join(env.umpire_data_dir, 'archived_dir')
    default_eventlog_dir = os.path.join(env.umpire_data_dir, 'eventlog')
    default_report_dir = os.path.join(env.umpire_data_dir, 'report')
    # TODO(rongchang): Implement the regcode API on Umpire.
    default_regcode_dir = os.path.join(env.umpire_data_dir, 'regcode')

    # TODO(itspeter): Remove the creation once the Umpire is taking care off.
    file_utils.TryMakeDirs(default_archived_dir)
    file_utils.TryMakeDirs(default_eventlog_dir)
    file_utils.TryMakeDirs(default_report_dir)
    file_utils.TryMakeDirs(default_regcode_dir)

    toolkit_dir = env.server_toolkits_dir

    regcode_default_key_pair = (
        os.path.join(toolkit_dir, REGCODE_DEFAULT_KEY),
        'google-crosreg-key')

    archiver_config = {
        'common': {
            'archived_dir': default_archived_dir,
            'project': env.config['board'],
            'duration': 'hourly'},
        'data_types': {
            'eventlog': {
                'notes': NOTES,
                'source_dir': default_eventlog_dir},
            'reports': {
                'notes': NOTES,
                'source_dir': default_report_dir,
                # TODO(itspeter): change it back to '.zip' once the backend
                #                 of report processing is completed.
                'compress_format': '.tar.xz',
                'duration': 'daily'},
            'regcode': {
                'notes': NOTES,
                'source_dir': default_regcode_dir,
                'encrypt_key_pair': regcode_default_key_pair}}}
    # Updates the field if defined in the archiver_config.
    ArchiverService._RecursivelyUpdateDict(
        archiver_config, umpire_config['services']['archiver'])

    # pprint guarantees the dictionary is sorted.
    config_hash = hashlib.md5(pprint.pformat(archiver_config)).hexdigest()
    config_path = os.path.join(
        env.config_dir, NAME_PREFIX + config_hash + '.yaml')

    with open(config_path, 'w') as fd:
      yaml_str = yaml.dump(archiver_config, default_flow_style=False)
      fd.write(yaml_str)
      logging.debug('Generated config at %r, content:\n%s\n',
                    config_path, yaml_str)
    return config_path

  def CreateProcesses(self, config, env):
    """Prepares the YAML configuration based on config.

    Args:
      config: Umpire config AttrDict object.
      env: UmpireEnv object.

    Returns:
      A list of ServiceProcess.
    """
    config_path = ArchiverService.GenerateConfig(config, env)

    proc_config = {
        'executable': os.path.join(env.server_toolkits_dir, 'active',
                                   ARCHIVER_CLI),
        'name': NAME_PREFIX + env.config['board'],
        'args': ['run', config_path],
        'path': '/tmp'}

    proc = umpire_service.ServiceProcess(self)
    proc.SetConfig(proc_config)
    return [proc]
